﻿using System;
using System.Collections;
using System.Collections.Generic;
using linq = Microsoft.Linq.Expressions;
using System.Linq;
using Bling.Util;
using Bling.DSL;
using Bling.Core;
namespace Bling.Statements {
  public abstract partial class StatementBuilder<SELF> where SELF : StatementBuilder<SELF> {

    public static implicit operator SELF(StatementBuilder<SELF> e) { return (SELF)e; }
    public abstract partial class Node {}
    public abstract partial class Unit : Node { }
    public abstract partial class HasResult<BRAND> : Node where BRAND : Brand<BRAND> {
      internal readonly BRAND Result;
      public HasResult() {
        Result = Params<BRAND>.Make(this);
      }
    }
    // user defined, produces a value...
    public abstract partial class Call<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> { }
    public partial class Condition<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> {
      public readonly Sequence IfTrue = new Sequence();
      public readonly Sequence IfFalse = new Sequence();
      public readonly BoolBl Test;
      internal BRAND IfTrueResult;
      internal BRAND IfFalseResult;
      public Condition(BoolBl Test) { 
        this.Test = Test; 
      }
    }
    public partial class WhileLoop : Node {
      public readonly Sequence Body = new Sequence();
      public readonly IntBl LoopVar;
      public BoolBl While { get; internal set; }
      public WhileLoop(IntBl LoopVar) {
        this.LoopVar = LoopVar; 
      }
    }
    public partial class ForLoop : WhileLoop {
      public readonly IntBl Limit;
      public ForLoop(IntBl LoopVar, IntBl Limit) : base(LoopVar) {
        this.Limit = Limit;
      }
    }
    public partial class SwitchCase<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> {
      public readonly Sequence[] Cases;
      public readonly IntBl Index;
      internal BRAND[] CaseResults;
      public SwitchCase(IntBl Index, int Count) {
        this.Index = Index;
        this.Cases = new Sequence[Count];
        for (int i = 0; i < Count; i++) 
          this.Cases[i] = new Sequence();
      }
    }
    public partial class Sequence {
      public readonly List<Node> Nodes = new List<Node>();
    }
    public readonly Sequence Top = new Sequence();
    private Sequence Current = null;

    public SELF Build(Action<SELF> Body) {
      (Current == null).Assert();
      (Top.Nodes.Count == 0).Assert();
      Current = Top;
      Body(this);
      (Current == Top).Assert();
      Current = null;
      return this;
    }
    public void Add(Unit e) {
      (Current != null).Assert();
      Current.Nodes.Add((e));
    }
    public BRAND Add<BRAND>(Call<BRAND> e) where BRAND : Brand<BRAND> {
      (Current != null).Assert();
      Current.Nodes.Add((e));
      return e.Result;
    }
    public void If(BoolBl Test, Action<SELF> IfTrue, Action<SELF> IfFalse) {
      (Current != null).Assert();
      var Node = new Condition<IntBl>(Test);
      var OldCurrent = Current;
      OldCurrent.Nodes.Add(Node);
      Current = Node.IfTrue;
      IfTrue(this);
      Current = Node.IfFalse;
      IfFalse(this);
      Current = OldCurrent; // pop.
    }
    public void If(BoolBl Test, Action<SELF> IfTrue) {
      If(Test, IfTrue, (unused) => { });
    }
    public BRAND If<BRAND>(BoolBl Test, Func<SELF, BRAND> IfTrue, Func<SELF, BRAND> IfFalse) where BRAND : Brand<BRAND> {
      (Current != null).Assert();
      var Node = new Condition<BRAND>(Test);
      var OldCurrent = Current;
      OldCurrent.Nodes.Add(Node);
      Current = Node.IfTrue;
      Node.IfTrueResult = IfTrue(this);
      Current = Node.IfFalse;
      Node.IfFalseResult = IfFalse(this);
      Current = OldCurrent; // pop.
      return Node.Result;
    }
    public void Do(Func<SELF, IntBl, BoolBl> Body) {
      (Current != null).Assert();
      IntBl loopVar = new ParameterExpr<int>();
      var OldCurrent = Current;
      var Node = new WhileLoop(loopVar);
      OldCurrent.Nodes.Add(Node);
      Current = Node.Body;
      Node.While = Body(this, loopVar);
      Current = OldCurrent;
    }
    public void For(IntBl Limit, Action<SELF, IntBl> Body) {
      (Current != null).Assert();
      IntBl loopVar = new LoopIndexExpr(Limit);
      var OldCurrent = Current;
      var Node = new ForLoop(loopVar,Limit);
      OldCurrent.Nodes.Add(Node);
      Current = Node.Body;
      Body(this, loopVar);
      Node.While = true.Bl() & (loopVar < Limit);
      Current = OldCurrent;
    }
    public void Switch(IntBl Index, params Action<SELF>[] Cases) {
      (Current != null).Assert();
      var OldCurrent = Current;
      var Node = new SwitchCase<IntBl>(Index, Cases.Length);
      OldCurrent.Nodes.Add(Node);
      for (int i = 0; i < Cases.Length; i++) {
        Current = Node.Cases[i];
        Cases[i](this);
      }
      Current = OldCurrent;
    }
    public BRAND Switch<BRAND>(IntBl Index, params Func<SELF,BRAND>[] Cases) where BRAND : Brand<BRAND> {
      (Current != null).Assert();
      var OldCurrent = Current;
      var Node = new SwitchCase<BRAND>(Index, Cases.Length);
      Node.CaseResults = new BRAND[Cases.Length];
      OldCurrent.Nodes.Add(Node);
      for (int i = 0; i < Cases.Length; i++) {
        Current = Node.Cases[i];
        Node.CaseResults[i] = Cases[i](this);
      }
      Current = OldCurrent;
      return Node.Result;
    }
  }
}
namespace Bling.Statements {
  using Bling.Shaders;
  using System.IO;
  public abstract partial class StatementBuilder<SELF> where SELF : StatementBuilder<SELF> {
    public abstract partial class Node {
      public abstract void Output<EVAL>(string Depth, StringWriter Out, EVAL Eval) where EVAL : BaseShaderEval<EVAL>;
      public abstract int Count(Func<Unit,int> EndPoint);
    }
    protected static void PushWrite<EVAL>(string Depth, Action<StringWriter> Body, StringWriter Out, EVAL Eval) where EVAL : BaseShaderEval<EVAL> {
      Eval.PushScope();
      var TempOut = new StringWriter();
      Body(TempOut);
      PopScope(Depth, Out, Eval);
      Out.Write(TempOut.ToString());
    }
    private static void PopScope<EVAL>(string Depth, System.IO.StringWriter Out, EVAL Eval) where EVAL:BaseShaderEval<EVAL>{
      var temps = Eval.PopScope();
      foreach (var t in temps) {
        Eval.Flatten(Depth, t.Code, Out);
        Out.WriteLine(Depth + Eval.TypeNameFor(((BaseShaderEval<EVAL>.MyInfo)t.AsTemp).TypeOfT) + " " +
          ((BaseShaderEval<EVAL>.MyInfo)t.AsTemp).Value + " = " + ((BaseShaderEval<EVAL>.MyInfo)t.Code).Value + ";");
      }
    }
    private static int VarCount = 0;
    private static string NewVar() {
      VarCount += 1;
      return "VV" + (VarCount - 1);
    }
    private class PutInScopeVisitor<EVAL> : ExprVisitor<int> where EVAL : BaseShaderEval<EVAL> {
      internal EVAL Eval;
      internal string name;
      public int Visit<T>(Expr<T> value) {
        var e = (ParameterExpr<T>)value;
        var info = Eval.NewInfo<T>(name);
        Eval.PutParamInScope(e, info);
        return 0;
      }

    }
    private static void PutInScope<BRAND,EVAL>(BRAND Brand, string name, EVAL Eval) where BRAND : Brand<BRAND> where EVAL : BaseShaderEval<EVAL> {
      Brand.Provider.Visit(new PutInScopeVisitor<EVAL>() { Eval = Eval, name = name });
    }
    public abstract partial class Unit : Node {
      public override int Count(Func<Unit, int> EndPoint) {
        return EndPoint(this);
      }
    }
    public abstract partial class Call<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> {
      public abstract string Output0<EVAL>(EVAL Eval);
      public override void Output<EVAL>(string Depth, StringWriter Out, EVAL Eval) {
        var ResultName = NewVar();
        Out.WriteLine(Depth + Eval.TypeNameFor(Result.Provider.TypeOfT) + " " + ResultName + ";");
        PutInScope(Result, ResultName, Eval);
        Out.WriteLine(Depth + ResultName + " = " + Output0(Eval) + ";");
      }
    }

    public partial class Condition<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> {
      public override void Output<EVAL>(string Depth, StringWriter Out, EVAL Eval) {
        var test = Eval.DoEval1(Test.Provider);
        string ConditionResultName = null;
        if (IfTrueResult != null) {
          ConditionResultName = NewVar();
          Out.WriteLine(Depth + Eval.TypeNameFor(Result.Provider.TypeOfT) + " " + ConditionResultName + ";");
          PutInScope(Result, ConditionResultName, Eval);
        }
        Eval.Flatten(Depth, test, Out);
        Out.WriteLine(Depth + "if (" + test.Value + ") {");
        IfTrue.Output(Depth + "  ", Out, Eval, ConditionResultName, IfTrueResult);
        if (IfFalse.Nodes.Count > 0) {
          Out.WriteLine(Depth + "} else {");
          IfFalse.Output(Depth + "  ", Out, Eval, ConditionResultName, IfFalseResult);
        }
        Out.WriteLine(Depth + "}");
      }
      public override int Count(Func<Unit, int> EndPoint) {
        return Math.Max(IfTrue.Count(EndPoint), IfFalse.Count(EndPoint));
      }
    }
    public partial class WhileLoop : Node {
      public override void Output<EVAL>(string Depth, StringWriter Out, EVAL Eval) {
        var LoopName = NewVar();
        var ContinueName = NewVar();
        Eval.PutParamInScope((ParameterExpr<int>)LoopVar.Provider, Eval.NewInfo<int>(LoopName));
        Out.WriteLine(Depth + "int " + LoopName + " = 0;");
        Out.WriteLine(Depth + "bool " + ContinueName + " = true;");
        Out.WriteLine(Depth + "{");
        PushWrite(Depth + "  ", (TempOut) => {
          var DoContinue = Eval.DoEval1(While.Provider);
          Eval.Flatten(Depth + "  ", DoContinue, TempOut);
          TempOut.WriteLine(Depth + "  " + ContinueName + " = " + DoContinue.Value + ";");
        }, Out, Eval);
        Out.WriteLine(Depth + "}");
        Out.WriteLine(Depth + "while (" + ContinueName + ") {");
        Body.Output(Depth + "  ", Out, Eval);

        Out.WriteLine(Depth + "  {");
        PushWrite(Depth + "    ", (TempOut) => {
          TempOut.WriteLine(Depth + "    " + LoopName + " = " + LoopName + " + 1;");
          var DoContinue = Eval.DoEval1(While.Provider);
          Eval.Flatten(Depth + "    ", DoContinue, TempOut);
          TempOut.WriteLine(Depth + "    " + ContinueName + " = " + DoContinue.Value + ";");
        }, Out, Eval);
        Out.WriteLine(Depth + "  }");
        Out.WriteLine(Depth + "}");
      }
      public override int Count(Func<Unit, int> EndPoint) {
        throw new NotSupportedException();
      }
    }
    public partial class ForLoop : WhileLoop {
      public override int Count(Func<Unit, int> EndPoint) {
        var limit = Limit.Extent(true);
        return limit.CurrentValue * Body.Count(EndPoint);
      }
    }
    public partial class SwitchCase<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> {
      public override void Output<EVAL>(string Depth, StringWriter Out, EVAL Eval) {
        string ResultName = null;
        if (CaseResults != null) {
          ResultName = NewVar();
          Out.WriteLine(Depth + Eval.TypeNameFor(Result.Provider.TypeOfT) + " " + ResultName + ";");
          PutInScope(Result, ResultName, Eval);
        }
        var index = Eval.DoEval1(Index.Provider);
        Eval.Flatten(Depth, index, Out);
        Out.WriteLine(Depth + "switch(" + index.Value + ") {");
        for (int i = 0; i < Cases.Length; i++) {
          Out.WriteLine(Depth + "  case " + i + ": { ");
          Cases[i].Output(Depth + "    ", Out, Eval, ResultName, CaseResults == null ? null : CaseResults[i]);
          Out.WriteLine(Depth + "  } break;");
        }
        Out.WriteLine("}");
      }
      public override int Count(Func<Unit, int> EndPoint) {
        int n = 0;
        foreach (var Case in Cases)
          n = Math.Max(n, Case.Count(EndPoint));
        return n;
      }
    }
    public partial class Sequence {
      public void Output<EVAL>(string Depth, StringWriter Out, EVAL Eval) where EVAL : BaseShaderEval<EVAL> {
        PushWrite(Depth, (TempOut) => {
          foreach (var n in Nodes) n.Output(Depth, TempOut, Eval);
        }, Out, Eval);
      }
      public void Output<EVAL,BRAND>(string Depth, StringWriter Out, EVAL Eval, string LHS, BRAND RHS) where EVAL : BaseShaderEval<EVAL> where BRAND : Brand<BRAND> {
        PushWrite(Depth, (TempOut) => {
          foreach (var n in Nodes) n.Output(Depth, TempOut, Eval);
          if (((object)RHS) != null) {
            (LHS != null).Assert();
            var info0 = Eval.DoEval1(RHS.Provider);
            Eval.Flatten(Depth, info0, TempOut);
            TempOut.WriteLine(Depth + LHS + " = " + info0.Value + ";");
          } else (LHS == null).Assert();
        }, Out, Eval);
      }
      public int Count(Func<Unit, int> EndPoint) {
        int n = 0;
        foreach (var node in Nodes) n += node.Count(EndPoint);
        return n;
      }
    }
  }
}
namespace Bling.Statements {
  using Bling.Linq;
  using Microsoft.Linq.Expressions;

  public abstract partial class StatementBuilder<SELF> where SELF : StatementBuilder<SELF> {
    public abstract partial class Node {
      public abstract Expression Eval<EVAL>(LinqEval<EVAL> txt) where EVAL : LinqEval<EVAL>;
    }
    public partial class Condition<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> {
      public override Expression Eval<EVAL>(LinqEval<EVAL> txt) {
        var test = txt.DoEval(Test.Provider).Value;
        var ifTrue = IfTrue.Eval(txt);
        var ifFalse = IfFalse.Eval(txt);
        return Expression.IfThenElse(test, ifTrue, ifFalse);
      }
    }
    public partial class WhileLoop : Node {
      public override Expression Eval<EVAL>(LinqEval<EVAL> txt) {
        txt.PushScope();
        var DoBreak = Expression.Label("BreakLoop");
        var LoopIndex = Expression.Parameter(typeof(int), "LoopI");
        txt.PutParamInScope((ParameterExpr<int>)LoopVar.Provider, new LinqEval<EVAL>.MyInfo<int>() {
          Value = LoopIndex,
        });
        Expression DoContinue;
        { // happens inside the loop.
          txt.PushScope();
          DoContinue = txt.DoEval(While.Provider).Value;
          DoContinue = Expression.Not(DoContinue);
          DoContinue = Expression.Goto(DoBreak, DoContinue);
          DoContinue = txt.PopScope(DoContinue);
        }
        var body = Body.Eval(txt);
        body = Expression.Block(DoContinue, body);
        body = Expression.Loop(body, DoBreak);
        body = Expression.Block(new ParameterExpression[] { LoopIndex }, body);
        return txt.PopScope(body);
      }
    }
    public partial class SwitchCase<BRAND> : HasResult<BRAND> where BRAND : Brand<BRAND> {
      public override Expression Eval<EVAL>(LinqEval<EVAL> txt) {
        var idx = txt.DoEval(Index.Provider).Value;
        var cases = new Microsoft.Linq.Expressions.SwitchCase[Cases.Length];
        for (int i = 0; i < cases.Length; i++) {
          cases[i] = Expression.SwitchCase(Cases[i].Eval(txt), Expression.Constant(i));
        }
        return Expression.Switch(idx, cases);
      }
    }

    public partial class Sequence {
      public Expression Eval<EVAL>(LinqEval<EVAL> txt) where EVAL : LinqEval<EVAL> {
        txt.PushScope();
        var Es = new List<Expression>();
        foreach (var node in Nodes) Es.Add(node.Eval(txt));
        var block = Expression.Block(Es.ToArray());
        return txt.PopScope(block);
      }
    }



  }
}
namespace Bling.Shaders {
  using Bling.Core;
  using Bling.DSL;
  using Bling.Statements;

  public abstract partial class BaseShaderEval<EVAL> : ScopedEval<EVAL>, Ops.IConditionEval2<EVAL>, ITableEval<EVAL>, Graphics.ISampleEval<EVAL>, Vecs.IVecEval<EVAL>, Vecs.IMatrixEval<EVAL>, WPF.IWPFEval<EVAL> where EVAL : Eval<EVAL> {


  }
}
